#include "pch.h"
#include "NativeUtils.h"
#if __has_include("NativeUtils.g.cpp")
#include "NativeUtils.g.cpp"
#endif

#include "Helpers/COMHelper.h"
#include "Helpers/LibraryHelper.h"
#include "InternalsRT/CoreWindowHelpers.h"

#include "FatalError.h"

#include <roerrorapi.h>
#include <detours.h>

#include <winrt/Windows.Foundation.h>
#include <winrt/Windows.Foundation.Collections.h>

typedef
BOOL
(APIENTRY* pGetKeyboardLayoutNameW)(
    _Out_ LPWSTR pwszKLID
    );

using namespace winrt::Windows::UI::Notifications;
using namespace winrt::Windows::ApplicationModel::Core;
using namespace winrt::Windows::Foundation::Collections;

namespace winrt::Telegram::Native::implementation
{
    FatalErrorCallback NativeUtils::Callback;

    PFN_RhGetCurrentObjSize NativeUtils::s_RhGetCurrentObjSize;

    void NativeUtils::SetFatalErrorCallback(FatalErrorCallback callback)
    {
        // TODO: td_set_log_message_callback
        //Client::SetLogMessageCallback(0, &NativeUtils::LogMessageCallback);
        Callback = callback;

        auto tdjson = GetModuleHandle(L"tdjson.dll");
        if (tdjson)
        {
            auto td_set_log_message_callback = reinterpret_cast<PFN_td_set_log_message_callback>(GetProcAddress(tdjson, "td_set_log_message_callback"));
            if (td_set_log_message_callback)
            {
                td_set_log_message_callback(0, &NativeUtils::LogMessageCallback);
            }
        }

        auto mrt100 = GetModuleHandle(L"mrt100_app.dll");
        if (mrt100)
        {
            s_RhGetCurrentObjSize = reinterpret_cast<PFN_RhGetCurrentObjSize>(GetProcAddress(mrt100, "RhGetCurrentObjSize"));

            if (s_RhGetCurrentObjSize)
            {
                DetourTransactionBegin();
                DetourUpdateThread(GetCurrentThread());

                DetourAttach(reinterpret_cast<PVOID*>(&s_RhGetCurrentObjSize), NativeUtils::RhGetCurrentObjSize);

                DetourTransactionCommit();
            }
        }
    }

    inline bool Contains(const hstring& message, std::wstring_view text)
    {
        return std::wstring_view{ message }.find(text) != std::wstring_view::npos;
    }

    inline bool Contains(const std::wstring& message, std::wstring_view text)
    {
        return message.find(text) != std::wstring::npos;
    }

    inline bool Contains(const std::string& message, std::string_view text)
    {
        return message.find(text) != std::string::npos;
    }

    inline bool IsDatabaseBrokenError(const std::string& message)
    {
        return Contains(message, "Wrong key or database is corrupted")
            || Contains(message, "SQL logic error or missing database")
            || Contains(message, "database disk image is malformed")
            || Contains(message, "file is encrypted or is not a database")
            || Contains(message, "unsupported file format")
            || Contains(message, "attempt to write a readonly database for database")
            || Contains(message, "file is not a database for database")
            || Contains(message, "Can't open database");
    }

    inline bool IsDiskFullError(const std::string& message)
    {
        return Contains(message, "There is not enough space on the disk")
            || Contains(message, ": 112 :")
            || Contains(message, "database or disk is full")
            || Contains(message, "out of memory for database");
    }

    inline bool IsDiskError(const std::string& message)
    {
        return Contains(message, "I/O error")
            || Contains(message, "Structure needs cleaning");
    }

    inline bool IsBinlogError(const std::string& message)
    {
        return Contains(message, "Failed to rename binlog")
            || Contains(message, "Can't rename")
            || Contains(message, "Failed to unlink old binlog")
            || Contains(message, "td.binlog")
            || Contains(message, ": 8 :")
            || Contains(message, ": 1392 :");
    }

    inline bool IsOutOfMemoryError(const std::string& message)
    {
        return Contains(message, "zlib deflate init failed")
            || Contains(message, "zlib inflate init failed")
            || Contains(message, "out of memory")
            || Contains(message, ": 1450 :");
    }

    void NativeUtils::LogMessageCallback(int verbosity_level, const char* msg)
    {
        std::string message = msg;
        if (NativeUtils::Callback)
        {
            if (IsDatabaseBrokenError(message))
            {
                return;
            }
            else if (IsDiskFullError(message))
            {
                return;
            }
            else if (IsDiskError(message))
            {
                return;
            }
            else if (IsBinlogError(message))
            {
                return;
            }
            else if (IsOutOfMemoryError(message))
            {
                return;
            }

            int bracketCount = 0;
            size_t start = std::string::npos, end = std::string::npos;

            for (size_t i = 0; i < message.length(); ++i)
            {
                if (message[i] == '[')
                {
                    bracketCount++;
                    if (bracketCount == 3)
                    {
                        start = i;
                    }
                }
                if (message[i] == ']' && bracketCount == 3)
                {
                    end = i;
                    break;
                }
            }

            if (start != std::string::npos && end != std::string::npos)
            {
                message.erase(start, end - start + 1);
            }

            NativeUtils::Callback(NativeUtils::GetBackTrace(L"TdException", winrt::to_hstring(message)));
        }
    }

    IXamlDirectObject NativeUtils::AddRunToCollection(XamlDirect direct, IXamlDirectObject inlines, hstring text, FlowDirection direction, TextStyle style, FontFamily fontFamily, double fontSize, bool transparent)
    {
        auto run = direct.CreateInstance(XamlTypeIndex::Run);
        direct.SetStringProperty(run, XamlPropertyIndex::Run_Text, text);
        direct.SetEnumProperty(run, XamlPropertyIndex::Run_FlowDirection, (uint32_t)direction);

        if ((style & TextStyle::Bold) != TextStyle::None)
        {
            direct.SetObjectProperty(run, XamlPropertyIndex::TextElement_FontWeight, winrt::box_value(FontWeights::SemiBold()));
        }

        if ((style & TextStyle::Italic) != TextStyle::None)
        {
            direct.SetEnumProperty(run, XamlPropertyIndex::TextElement_FontStyle, (uint32_t)FontStyle::Italic);
        }

        auto decorations = TextDecorations::None;
        if ((style & TextStyle::Underline) != TextStyle::None)
        {
            decorations |= TextDecorations::Underline;
        }
        if ((style & TextStyle::Strikethrough) != TextStyle::None)
        {
            decorations |= TextDecorations::Strikethrough;
        }

        if (decorations != TextDecorations::None)
        {
            direct.SetEnumProperty(run, XamlPropertyIndex::TextElement_TextDecorations, (uint32_t)decorations);
        }

        if (fontFamily)
        {
            direct.SetObjectProperty(run, XamlPropertyIndex::TextElement_FontFamily, fontFamily);
        }

        if (fontSize > 0)
        {
            direct.SetDoubleProperty(run, XamlPropertyIndex::TextElement_FontSize, fontSize);
        }

        // TODO: removed once fixed by Microsoft
        if (transparent)
        {
            direct.SetObjectProperty(run, XamlPropertyIndex::TextElement_Foreground, nullptr);
        }

        direct.AddToCollection(inlines, run);
        return run;
    }

    IXamlDirectObject NativeUtils::AddRunToCollection(XamlDirect direct, IXamlDirectObject inlines, hstring text, int32_t offset, int32_t length, FlowDirection direction, TextStyle style, FontFamily fontFamily, double fontSize, bool transparent)
    {
        std::wstring wstr = text.c_str();
        auto run = direct.CreateInstance(XamlTypeIndex::Run);
        direct.SetStringProperty(run, XamlPropertyIndex::Run_Text, hstring(wstr.substr(offset, length)));
        direct.SetEnumProperty(run, XamlPropertyIndex::Run_FlowDirection, (uint32_t)direction);

        if ((style & TextStyle::Bold) != TextStyle::None)
        {
            direct.SetObjectProperty(run, XamlPropertyIndex::TextElement_FontWeight, winrt::box_value(FontWeights::SemiBold()));
        }

        if ((style & TextStyle::Italic) != TextStyle::None)
        {
            direct.SetEnumProperty(run, XamlPropertyIndex::TextElement_FontStyle, (uint32_t)FontStyle::Italic);
        }

        auto decorations = TextDecorations::None;
        if ((style & TextStyle::Underline) != TextStyle::None)
        {
            decorations |= TextDecorations::Underline;
        }
        if ((style & TextStyle::Strikethrough) != TextStyle::None)
        {
            decorations |= TextDecorations::Strikethrough;
        }

        if (decorations != TextDecorations::None)
        {
            direct.SetEnumProperty(run, XamlPropertyIndex::TextElement_TextDecorations, (uint32_t)decorations);
        }

        if (fontFamily)
        {
            direct.SetObjectProperty(run, XamlPropertyIndex::TextElement_FontFamily, fontFamily);
        }

        if (fontSize > 0)
        {
            direct.SetDoubleProperty(run, XamlPropertyIndex::TextElement_FontSize, fontSize);
        }

        // TODO: removed once fixed by Microsoft
        if (transparent)
        {
            direct.SetObjectProperty(run, XamlPropertyIndex::TextElement_Foreground, nullptr);
        }

        direct.AddToCollection(inlines, run);
        return run;
    }

    winrt::Telegram::Native::FatalError NativeUtils::GetStowedException()
    {
        HRESULT result;

        winrt::com_ptr<IRestrictedErrorInfo> info;
        //winrt::com_ptr<ILanguageExceptionErrorInfo2> info2;
        //winrt::com_ptr<IUnknown> language;
        winrt::com_ptr<IRestrictedErrorInfoContext> context;
        STOWED_EXCEPTION_INFORMATION_V2* stowed;

        CleanupIfFailed(result, GetRestrictedErrorInfo(info.put()));
        //CleanupIfFailed(result, info->QueryInterface(info2.put()));
        //CleanupIfFailed(result, info2->GetLanguageException(language.put()));

        //if (language != nullptr && onlyNative)
        //{
        //    // Language exceptions are from CoreCLR
        //    return nullptr;
        //}

        if (info == nullptr)
        {
            return nullptr;
        }

        CleanupIfFailed(result, SetRestrictedErrorInfo(info.get()));

        // TODO: Currently unused, we still propagate the managed exception and we get details from there
        // Would be fine to use this method, but strings are a little messed up:
        // "description" contains the exception message
        // "restrictedDescription" contains the exception message + stack trace
        //HRESULT error;
        //BSTR description, restrictedDescription, capabilitySid;
        //info->GetErrorDetails(&description, &error, &restrictedDescription, &capabilitySid);

        CleanupIfFailed(result, info->QueryInterface(context.put()));

        if (context == nullptr)
        {
            return nullptr;
        }

        CleanupIfFailed(result, context->GetContext(&stowed));

        return GetStowedException2(stowed);

    Cleanup:
        return nullptr;
    }

    winrt::Telegram::Native::FatalError NativeUtils::GetStowedException2(STOWED_EXCEPTION_INFORMATION_V2* stowed)
    {
        HRESULT result;

        if (stowed != nullptr && stowed->ExceptionForm == 1 && stowed->Header.Signature == 'SE02')
        {
            auto frames = winrt::single_threaded_vector<FatalErrorFrame>();

            for (int i = 0; i < stowed->StackTraceWords; ++i)
            {
                PVOID pointer;
                if (stowed->StackTraceWordSize == 4)
                {
                    auto addresses = (UINT32**)stowed->StackTrace;
                    pointer = *(addresses + i);
                }
                else if (stowed->StackTraceWordSize == 8)
                {
                    auto addresses = (UINT64**)stowed->StackTrace;
                    pointer = *(addresses + i);
                }
                else
                {
                    continue;
                }

                void* moduleBaseVoid = nullptr;
                RtlPcToFileHeader(pointer, &moduleBaseVoid);

                auto moduleBase = (const unsigned char*)moduleBaseVoid;
                if (moduleBase != nullptr)
                {
                    frames.Append({ (intptr_t)pointer, (intptr_t)moduleBase });
                }
                else
                {
                    //trace += wstrprintf(L"   at %s+0x%016llx\n", L"unknown", (uint64_t)pointer);
                }
            }

            if (frames.Size())
            {
                auto error = winrt::Telegram::Native::FatalError(L"", L"", L"", frames);

                if (stowed->NestedExceptionType == STOWED_EXCEPTION_NESTED_TYPE_STOWED)
                {
                    error.InnerException(GetStowedException2((STOWED_EXCEPTION_INFORMATION_V2*)stowed->NestedException));
                }

                return error;
            }
        }

    Cleanup:
        return nullptr;
    }

    // From http://davidpritchard.org/archives/907
    winrt::Telegram::Native::FatalError NativeUtils::GetBackTrace(hstring type, hstring message)
    {
        constexpr uint32_t TRACE_MAX_STACK_FRAMES = 99;
        void* stack[TRACE_MAX_STACK_FRAMES];

        ULONG hash;
        const int numFrames = CaptureStackBackTrace(1, TRACE_MAX_STACK_FRAMES, stack, &hash);
        auto frames = winrt::single_threaded_vector<FatalErrorFrame>();

        std::wstring trace;
        bool skipping = false;

        for (int i = 0; i < numFrames; ++i)
        {
            PVOID pointer = (unsigned char*)stack[i];

            void* moduleBaseVoid = nullptr;
            RtlPcToFileHeader(stack[i], &moduleBaseVoid);

            auto moduleBase = (const unsigned char*)moduleBaseVoid;
            wchar_t modulePath[MAX_PATH];

            if (moduleBase != nullptr)
            {
                GetModuleFileName((HMODULE)moduleBase, modulePath, MAX_PATH);

                auto moduleFilename = std::wstring(modulePath);

                int moduleFilenamePos = moduleFilename.find_last_of(L"\\");
                if (moduleFilenamePos >= 0)
                {
                    moduleFilename = moduleFilename.substr(moduleFilenamePos + 1);
                }

                trace += wstrprintf(L"   at %s+0x%08lx\n", moduleFilename.c_str(), (uint32_t)((unsigned char*)pointer - moduleBase));
                frames.Append({ (intptr_t)pointer, (intptr_t)moduleBase });
            }
        }

        if (type.empty())
        {
            if (Contains(trace, L"libvlc.dll") || Contains(trace, L"libvlccore.dll"))
            {
                type = L"VLCException";
            }
            else if (Contains(trace, L"Telegram.Native.Calls.dll"))
            {
                type = L"VoipException";
            }
            else if (Contains(trace, L"Telegram.Td.dll"))
            {
                type = L"TdException";
            }
            else
            {
                type = L"NativeException";
            }
        }

        auto error = winrt::make_self<FatalError>(type, message, hstring(trace), frames);
        return error.as<winrt::Telegram::Native::FatalError>();
    }

    bool NativeUtils::FileExists(hstring path)
    {
        WIN32_FILE_ATTRIBUTE_DATA fileInfo;
        return GetFileAttributesExFromAppW(path.data(), GetFileExInfoStandard, (void*)&fileInfo);
    }

    int64_t NativeUtils::GetDirectorySize(hstring path)
    {
        return GetDirectorySize(path, L"\\*");
    }

    int64_t NativeUtils::GetDirectorySize(hstring path, hstring filter)
    {
        return GetDirectorySizeInternal(path.data(), filter.data(), 0);
    }

    void NativeUtils::CleanDirectory(hstring path, int days)
    {
        CleanDirectoryInternal(path.data(), days);
    }

    void NativeUtils::Delete(hstring path)
    {
        DeleteFile(path.data());
    }

    void NativeUtils::CleanDirectoryInternal(const std::wstring& path, int days)
    {
        long diff = 60 * 60 * 1000 * 24 * days;

        FILETIME ft;
        GetSystemTimeAsFileTime(&ft);
        auto currentTime = FileTimeToSeconds(ft);

        WIN32_FIND_DATA data;
        HANDLE handle = FindFirstFile((path + L"\\*").c_str(), &data);

        if (handle == INVALID_HANDLE_VALUE)
        {
            return;
        }

        do
        {
            if (IsBrowsePath(data.cFileName))
            {
                continue;
            }

            if ((data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) == FILE_ATTRIBUTE_DIRECTORY)
            {
                CleanDirectoryInternal(path + L"\\" + data.cFileName, days);
            }
            else
            {
                auto lastAccess = FileTimeToSeconds(data.ftLastAccessTime);
                auto lastWrite = FileTimeToSeconds(data.ftLastWriteTime);

                if (days == 0)
                {
                    DeleteFile((path + L"\\" + data.cFileName).c_str());
                }
                else if (lastAccess > lastWrite)
                {
                    if (lastAccess + diff < currentTime)
                    {
                        DeleteFile((path + L"\\" + data.cFileName).c_str());
                    }
                }
                else if (lastWrite + diff < currentTime)
                {
                    DeleteFile((path + L"\\" + data.cFileName).c_str());
                }
            }

        } while (FindNextFile(handle, &data));

        FindClose(handle);
    }

    uint64_t NativeUtils::GetDirectorySizeInternal(const std::wstring& path, const std::wstring& filter, uint64_t size)
    {
        WIN32_FIND_DATA data;
        HANDLE handle = FindFirstFile((path + filter).c_str(), &data);

        if (handle == INVALID_HANDLE_VALUE)
        {
            return size;
        }

        do
        {
            if (IsBrowsePath(data.cFileName))
            {
                continue;
            }

            if ((data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) == FILE_ATTRIBUTE_DIRECTORY)
            {
                size = GetDirectorySizeInternal(path + L"\\" + data.cFileName, filter, size);
            }
            else
            {
                size += (uint64_t)(data.nFileSizeHigh * (MAXDWORD)+data.nFileSizeLow);
            }

        } while (FindNextFile(handle, &data));

        FindClose(handle);

        return size;
    }

    bool NativeUtils::IsBrowsePath(const std::wstring& path)
    {
        return (path.find(L".") == 0 || path.find(L"..") == 0);
    }

    ULONGLONG NativeUtils::FileTimeToSeconds(FILETIME& ft)
    {
        ULARGE_INTEGER uli;
        uli.HighPart = ft.dwHighDateTime;
        uli.LowPart = ft.dwLowDateTime;

        return uli.QuadPart / 10000;
    }

    int32_t NativeUtils::GetLastInputTime()
    {
        typedef BOOL(WINAPI* pGetLastInputInfo)(_Out_ PLASTINPUTINFO);

        static const LibraryInstance user32(L"User32.dll", 0x00000001);
        static const auto getLastInputInfo = user32.GetMethod<pGetLastInputInfo>("GetLastInputInfo");

        if (getLastInputInfo == nullptr)
        {
            return 0;
        }

        LASTINPUTINFO lastInput;
        lastInput.cbSize = sizeof(LASTINPUTINFO);

        if (getLastInputInfo(&lastInput))
        {
            return lastInput.dwTime;
        }

        return 0;
    }

    winrt::Telegram::Native::TextDirectionality NativeUtils::GetDirectionality(hstring value)
    {
        return GetDirectionality(value, 0, value.size());
    }

    winrt::Telegram::Native::TextDirectionality NativeUtils::GetDirectionality(hstring value, int32_t offset)
    {
        return GetDirectionality(value, offset, value.size() - offset);
    }

    winrt::Telegram::Native::TextDirectionality NativeUtils::GetDirectionality(hstring value, int32_t offset, int32_t length)
    {
        DWORD prev = C2_OTHERNEUTRAL;
        for (int i = 0; i < length; i++)
        {
            if (IS_HIGH_SURROGATE(value[offset + i]) || IS_LOW_SURROGATE(value[offset + i]))
            {
                continue;
            }

            WORD type;
            GetStringTypeEx(LOCALE_USER_DEFAULT, CT_CTYPE2, value.data() + offset + i, 1, &type);

            // We use the first strong character after a neutral character.
            if (prev >= C2_BLOCKSEPARATOR && prev <= C2_OTHERNEUTRAL)
            {
                if (type == C2_LEFTTORIGHT)
                {
                    return winrt::Telegram::Native::TextDirectionality::LeftToRight;
                }
                else if (type == C2_RIGHTTOLEFT)
                {
                    return winrt::Telegram::Native::TextDirectionality::RightToLeft;
                }
            }

            prev = type;
        }

        return winrt::Telegram::Native::TextDirectionality::Neutral;
    }

    hstring NativeUtils::GetCurrentCulture()
    {
        TCHAR buff[LOCALE_NAME_MAX_LENGTH];
        int result = GetLocaleInfoEx(LOCALE_NAME_USER_DEFAULT, LOCALE_SNAME, buff, LOCALE_NAME_MAX_LENGTH);
        if (result == 0)
        {
            result = GetLocaleInfoEx(LOCALE_NAME_SYSTEM_DEFAULT, LOCALE_SNAME, buff, LOCALE_NAME_MAX_LENGTH);
            if (result == 0)
            {
                return L"en";
            }
        }

        std::wstring str = buff;
        size_t sorting = str.find(L"_");

        if (sorting != std::wstring::npos)
        {
            return str.substr(0, sorting).c_str();
        }

        return buff;
    }

    hstring NativeUtils::GetKeyboardCulture()
    {
        // TODO: I'm not sure about how much expensive this call is.
        // At the moment it isn't used extremely often, but we should
        // consider caching it (problem is how to invalidate the cache)
        static const LibraryInstance user32(L"User32.dll");
        static const auto getKeyboardLayoutName = user32.GetMethod<pGetKeyboardLayoutNameW>("GetKeyboardLayoutNameW");

        WCHAR name[KL_NAMELENGTH];
        if (getKeyboardLayoutName(name))
        {
            // The layout name looks something like this: 00000410
            // Where the first 4 bytes are most likely flags
            // And the second half is actually the LCID as a HEX string
            unsigned int lcid = std::stoul(name + 4, nullptr, 16);

            WCHAR locale[LOCALE_NAME_MAX_LENGTH];
            int length = LCIDToLocaleName(lcid, locale, LOCALE_NAME_MAX_LENGTH, 0);

            if (length > 0)
            {
                // The string is null terminated
                return hstring(locale, length - 1);
            }
        }

        // TODO: probably better this than an empty string.
        return L"en-US";
    }

    inline static hstring GetDateFormatEx(CONST SYSTEMTIME* lpDate, hstring format)
    {
        DWORD flags = NULL;
        LPCWSTR formatData = NULL;

        if (format == L"DATE_LONGDATE")
        {
            flags = DATE_LONGDATE;
        }
        else if (format == L"DATE_SHORTDATE")
        {
            flags = DATE_SHORTDATE;
        }
        else
        {
            formatData = format.data();
        }

        TCHAR dateString[256];
        if (GetDateFormatEx(LOCALE_NAME_USER_DEFAULT, flags, lpDate, formatData, dateString, 256, NULL))
        {
            return hstring(dateString);
        }

        return hstring();
    }

    hstring NativeUtils::FormatDate(winrt::Windows::Foundation::DateTime value, hstring format)
    {
        FILETIME fileTime = winrt::clock::to_file_time(value);
        SYSTEMTIME systemTime;
        if (FileTimeToSystemTime(&fileTime, &systemTime))
        {
            SYSTEMTIME localSystemTime;
            if (SystemTimeToTzSpecificLocalTime(NULL, &systemTime, &localSystemTime))
            {
                return GetDateFormatEx(&localSystemTime, format);
            }
        }

        return hstring();
    }

    hstring NativeUtils::FormatDate(int year, int month, int day, hstring format)
    {
        SYSTEMTIME systemTime;
        systemTime.wYear = year;
        systemTime.wMonth = month;
        systemTime.wDay = day;
        systemTime.wHour = 12;

        return GetDateFormatEx(&systemTime, format);
    }

    hstring NativeUtils::FormatTime(int value)
    {
        FILETIME fileTime;
        ULARGE_INTEGER uli;
        uli.QuadPart = (static_cast<ULONGLONG>(value) + 11644473600LL) * 10000000LL;
        fileTime.dwLowDateTime = uli.LowPart;
        fileTime.dwHighDateTime = uli.HighPart;

        SYSTEMTIME systemTime;
        if (FileTimeToSystemTime(&fileTime, &systemTime))
        {
            SYSTEMTIME localSystemTime;
            if (SystemTimeToTzSpecificLocalTime(NULL, &systemTime, &localSystemTime))
            {
                TCHAR timeString[128];
                if (GetTimeFormatEx(LOCALE_NAME_USER_DEFAULT, TIME_NOSECONDS, &localSystemTime, nullptr, timeString, 128))
                {
                    return hstring(timeString);
                }
            }
        }

        return hstring();
    }

    hstring NativeUtils::FormatTime(winrt::Windows::Foundation::DateTime value)
    {
        FILETIME fileTime = winrt::clock::to_file_time(value);
        SYSTEMTIME systemTime;
        if (FileTimeToSystemTime(&fileTime, &systemTime))
        {
            SYSTEMTIME localSystemTime;
            if (SystemTimeToTzSpecificLocalTime(NULL, &systemTime, &localSystemTime))
            {
                TCHAR timeString[128];
                if (GetTimeFormatEx(LOCALE_NAME_USER_DEFAULT, TIME_NOSECONDS, &localSystemTime, nullptr, timeString, 128))
                {
                    return hstring(timeString);
                }

                //switch (GetLastError())
                //{
                //case ERROR_INSUFFICIENT_BUFFER:
                //    return L"E_INSUFFICIENT_BUFFER";
                //case ERROR_INVALID_FLAGS:
                //    return L"E_INVALID_FLAGS";
                //case ERROR_INVALID_PARAMETER:
                //    return L"E_INVALID_PARAMETER";
                //case ERROR_OUTOFMEMORY:
                //    return L"E_OUTOFMEMORY";
                //default:
                //    return L"E_UNKNOWN";
                //}
            }
        }

        return hstring();
    }

    hstring NativeUtils::FormatDate(int value, hstring format)
    {
        FILETIME fileTime;
        ULARGE_INTEGER uli;
        uli.QuadPart = (static_cast<ULONGLONG>(value) + 11644473600LL) * 10000000LL;
        fileTime.dwLowDateTime = uli.LowPart;
        fileTime.dwHighDateTime = uli.HighPart;

        SYSTEMTIME systemTime;
        if (FileTimeToSystemTime(&fileTime, &systemTime))
        {
            SYSTEMTIME localSystemTime;
            if (SystemTimeToTzSpecificLocalTime(NULL, &systemTime, &localSystemTime))
            {
                return GetDateFormatEx(&localSystemTime, format);
            }
        }

        return hstring();
    }

    bool NativeUtils::IsFileReadable(hstring path)
    {
        return IsFileReadableInternal(path, NULL, NULL);
    }

    bool NativeUtils::IsFileReadable(hstring path, int64_t& fileSize, int64_t& fileTime)
    {
        return IsFileReadableInternal(path, &fileSize, &fileTime);
    }

    bool NativeUtils::IsFileReadableInternal(hstring path, int64_t* fileSize, int64_t* fileTime)
    {
        DWORD desired_access = GENERIC_READ;

        // TODO: share mode
        DWORD share_mode = FILE_SHARE_READ | FILE_SHARE_DELETE | FILE_SHARE_WRITE;

        DWORD creation_disposition = OPEN_ALWAYS;

        DWORD native_flags = FILE_FLAG_BACKUP_SEMANTICS;
        //if (flags & Direct) {
        //	native_flags |= FILE_FLAG_WRITE_THROUGH | FILE_FLAG_NO_BUFFERING;
        //}
        //if (flags & WinStat) {
        //	native_flags |= FILE_FLAG_BACKUP_SEMANTICS;
        //}
        CREATEFILE2_EXTENDED_PARAMETERS extended_parameters;
        std::memset(&extended_parameters, 0, sizeof(extended_parameters));
        extended_parameters.dwSize = sizeof(extended_parameters);
        extended_parameters.dwFileAttributes = FILE_ATTRIBUTE_NORMAL;
        extended_parameters.dwFileFlags = native_flags;
        auto handle = CreateFile2FromAppW(path.c_str(), desired_access, share_mode, creation_disposition, &extended_parameters);

        if (handle == INVALID_HANDLE_VALUE)
        {
            return false;
        }

        if (fileSize)
        {
            LARGE_INTEGER pFileSize;
            GetFileSizeEx(handle, &pFileSize);

            *fileSize = static_cast<int64_t>(pFileSize.QuadPart);
        }

        if (fileTime)
        {
            FILETIME pFileTime;
            GetFileTime(handle, NULL, NULL, &pFileTime);

            *fileTime = static_cast<int64_t>(FileTimeToSeconds(pFileTime));
        }

        CloseHandle(handle);
        return true;
    }

    bool NativeUtils::IsMediaSupported()
    {
        HRESULT result;
        result = MFStartup(MF_VERSION);

        if (result == S_OK)
        {
            MFShutdown();
        }

        return result != E_NOTIMPL;
    }

    void NativeUtils::OverrideScaleForCurrentView(int32_t value)
    {
        InternalsRT::Core::Windowing::CoreWindowHelpers::OverrideDpiForCurrentThread(value * 96.0f / 100.0f);
    }

    int32_t NativeUtils::GetScaleForCurrentView()
    {
        return InternalsRT::Core::Windowing::CoreWindowHelpers::GetDpiForCurrentThread() / 96.0f * 100.0f;
    }

    void NativeUtils::Crash()
    {
        std::thread([]() {
            int x = 1;
            int y = 0;
            int z = x / y;
            }).detach();
        return;

        int32_t* ciao = nullptr;
        *ciao = 42;
    }

    hstring NativeUtils::GetLogMessage(int64_t format, int64_t args)
    {
        int byteLength = vsnprintf(NULL, NULL, (char*)format, (va_list)args) + 1;
        if (byteLength <= 1)
            return L"";

        char* buffer = new char[byteLength];
        vsprintf(buffer, (char*)format, (va_list)args);
        hstring result = winrt::to_hstring(std::string(buffer, byteLength - 1));
        delete[] buffer;
        return result;
    }

} // namespace winrt::Telegram::Native::implementation
